package com.ruoyi.qif.service.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.qif.domain.BillImageUploadDTO;
import com.ruoyi.qif.domain.QifRoom;
import com.ruoyi.qif.mapper.QifRoomMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.ruoyi.qif.mapper.QifBillMapper;
import com.ruoyi.qif.domain.QifBill;
import com.ruoyi.qif.service.IQifBillService;
import com.ruoyi.common.core.text.Convert;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;

import static com.ruoyi.common.utils.ShiroUtils.getLoginName;

/**
 * 琦风公寓-每月账单Service业务层处理
 *
 * @author David
 * @date 2025-10-05
 */
@Service
public class QifBillServiceImpl implements IQifBillService {
    @Autowired
    private QifBillMapper qifBillMapper;
    @Autowired
    private QifRoomMapper qifRoomMapper;
    /**
     * 读取若依默认文件存储根路径（ruoyi.profile）
     * 对应配置：ruoyi: profile: D:/ruoyi/uploadPath
     */
    @Value("${ruoyi.profile}")
    private String ruoyiProfilePath;

    /**
     * 账单图片子目录（在若依profile路径下，用于分类存储账单图片）
     * 常量定义：固定为"bill-images"，与业务含义匹配
     */
    private static final String BILL_IMAGE_SUB_DIR = "bill-images";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String uploadBillImage(BillImageUploadDTO dto) throws Exception {
        // 1. 校验账单是否存在
        QifBill bill = qifBillMapper.selectQifBillByBillId(dto.getBillId());
        if (bill == null) {
            throw new ServiceException("账单不存在，无法上传图片！");
        }

        // 补充获取单元号和房间号（假设DTO中包含这两个字段，若从账单实体获取可调整）
        String unitNo = bill.getUnit(); // 单元号
        String roomNo = bill.getRoomNum(); // 房间号
        if (unitNo == null || unitNo.trim().isEmpty() || roomNo == null || roomNo.trim().isEmpty()) {
            throw new ServiceException("单元号和房间号不能为空！");
        }

        // 2. 处理Base64字符串（保持原逻辑）
        String base64Str = dto.getBillImageBase64();
        if (base64Str == null || base64Str.isEmpty()) {
            throw new ServiceException("图片Base64字符串不能为空！");
        }
        if (!base64Str.startsWith("data:image/")) {
            throw new ServiceException("图片Base64格式错误，需包含data:image/前缀（如data:image/png;base64,）！");
        }
        String[] base64Parts = base64Str.split(",");
        if (base64Parts.length < 2) {
            throw new ServiceException("图片Base64格式错误，未找到有效数据部分！");
        }
        String pureBase64 = base64Parts[1]
                .replaceAll("[^A-Za-z0-9+/=]", "")
                .trim();
        if (pureBase64.isEmpty()) {
            throw new ServiceException("图片Base64数据为空，请重新生成！");
        }

        // 3. 解码Base64
        byte[] imageBytes;
        try {
            imageBytes = Base64.getDecoder().decode(pureBase64);
        } catch (IllegalArgumentException e) {
            throw new ServiceException("图片Base64解码失败，可能包含非法字符！");
        }

        // 4. 校验图片有效性
        if (!isValidImage(imageBytes)) {
            throw new ServiceException("上传的不是有效图片文件，请重新生成账单图片！");
        }

        // 5. 生成存储路径（保持原逻辑）
        String rootStoreDir = ruoyiProfilePath + File.separator + BILL_IMAGE_SUB_DIR;
        String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
        String fullStoreDir = rootStoreDir + File.separator + dateDir;

        // 创建目录
        File dirFile = new File(fullStoreDir);
        if (!dirFile.exists() && !dirFile.mkdirs()) {
            throw new ServiceException("账单图片存储目录创建失败，请检查若依profile路径权限！");
        }

        // 6. 生成新文件名（格式：账单ID-年月-单元号-房间号.png）
        String fileSuffix = "png";
        String newFileName = String.format("%s-%s-%s-%s.%s",
                dto.getBillId(),
                dateDir, // 年月格式与目录一致（202511）
                unitNo,
                roomNo,
                fileSuffix);
        String fullStorePath = fullStoreDir + File.separator + newFileName;

        // 7. 删除同账单的旧文件（关键修改）
        // 构建旧文件匹配规则：账单ID-任意年月-单元号-房间号.png
        String oldFilePattern = String.format("%s-\\d{6}-%s-%s\\.%s",
                dto.getBillId(),
                unitNo,
                roomNo,
                fileSuffix);
        File[] oldFiles = dirFile.listFiles((dir, name) -> name.matches(oldFilePattern));
        if (oldFiles != null) {
            for (File oldFile : oldFiles) {
                if (!oldFile.delete()) {
                    throw new ServiceException("删除旧旧图片文件失败，请检查文件权限！");
                }
            }
        }

        // 8. 保存新图片
        Path path = Paths.get(fullStorePath);
        Files.write(path, imageBytes);

        // 9. 更新账单图片URL
        String imageUrl = Constants.RESOURCE_PREFIX + File.separator + BILL_IMAGE_SUB_DIR
                + File.separator + dateDir + File.separator + newFileName;
        imageUrl = imageUrl.replace("\\", "/");

        bill.setBillChart(imageUrl);
        int updateRows = qifBillMapper.updateQifBill(bill);
        if (updateRows == 0) {
            Files.deleteIfExists(path);
            throw new ServiceException("账单图片地址更新失败，请重试！");
        }

        return imageUrl;
    }

    /**
     * 辅助方法：校验字节数组是否为有效图片
     * @param imageBytes 图片字节数组
     * @return true=有效图片，false=无效
     */
    private boolean isValidImage(byte[] imageBytes) {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes)) {
            BufferedImage image = ImageIO.read(inputStream);
            // 校验：图片非空且宽高大于0（避免空白图片）
            return image != null && image.getWidth() > 0 && image.getHeight() > 0;
        } catch (IOException e) {
            return false;
        }
    }
    /**
     * 查询琦风公寓-每月账单
     *
     * @param billId 琦风公寓-每月账单主键
     * @return 琦风公寓-每月账单
     */
    @Override
    public QifBill selectQifBillByBillId(Long billId) {
        return qifBillMapper.selectQifBillByBillId(billId);
    }
    /**
     * 获取本月账单中所有去重的交租日
     * @return 交租日列表（Long类型，如[1,5,10,20]）
     */
    @Override
    public List<Long> getDistinctRentPayDates() {
        // 计算当前月份的起止日期
        YearMonth currentMonth = YearMonth.now();
        LocalDate firstDay = currentMonth.atDay(1);
        LocalDate lastDay = currentMonth.atEndOfMonth();

        // 转换为Date类型（匹配数据库字段）
        Date startDate = Date.from(firstDay.atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date endDate = Date.from(lastDay.atStartOfDay(ZoneId.systemDefault()).toInstant());

        // 调用Mapper查询本月账单中去重的交租日
        return qifBillMapper.selectDistinctRentPayDates(startDate, endDate);
    }
    /**
     * 查询琦风公寓-每月账单列表
     *
     * @param qifBill 琦风公寓-每月账单
     * @return 琦风公寓-每月账单
     */
    @Override
    public List<QifBill> selectQifBillList(QifBill qifBill) {
        // 初始化参数Map（若依推荐用params存储额外条件）
        if (qifBill.getParams() == null) {
            qifBill.setParams(new HashMap<>(2));
        }

        // 获取当前年月（默认查询条件）
        Calendar cal = Calendar.getInstance();
        int year = qifBill.getQueryYear() != null ? qifBill.getQueryYear() : cal.get(Calendar.YEAR);
        int month = qifBill.getQueryMonth() != null ? qifBill.getQueryMonth() : (cal.get(Calendar.MONTH) + 1); // 月份+1（Calendar月份从0开始）

        // 2. 生成字符串格式的起止日期（yyyy-MM-dd）
        String startDateStr = String.format("%d-%02d-01", year, month); // 当月1日（如2025-10-01）
        // 计算当月最后一天（处理2月、大月小月）
        YearMonth yearMonth = YearMonth.of(year, month);
        int lastDay = yearMonth.lengthOfMonth();
        String endDateStr = String.format("%d-%02d-%02d", year, month, lastDay); // 当月最后一天（如2025-10-31）

        // 3. 存入params（字符串类型）
        qifBill.getParams().put("billDateStart", startDateStr);
        qifBill.getParams().put("billDateEnd", endDateStr);


        return qifBillMapper.selectQifBillList(qifBill);
    }

    /**
     * 新增琦风公寓-每月账单
     *
     * @param qifBill 琦风公寓-每月账单
     * @return 结果
     */
    @Override
    public int insertQifBill(QifBill qifBill) {
        qifBill.setCreateTime(DateUtils.getNowDate());
        return qifBillMapper.insertQifBill(qifBill);
    }
    /**
     * 生成本月账单
     *
     * @return 生成的账单数量
     */
    @Override
    @Transactional
    public int generateCurrentMonthBills() {
        // 1. 获取所有在租的房间
        QifRoom queryRoom = new QifRoom();
        queryRoom.setStatus("0");
        queryRoom.setDelFlag("0");
        List<QifRoom> rentingRooms = qifRoomMapper.selectQifRoomList(queryRoom);

        if (rentingRooms == null || rentingRooms.isEmpty()) {
            return 0;
        }

        // 2. 日期格式化器（用于账单日期计算）
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
                .withZone(ZoneId.systemDefault());
        int operateCount = 0;

        // 3. 遍历房间处理账单（每个房间生成唯一Long类型编号）
        for (QifRoom room : rentingRooms) {
            // 计算账单日期（基于交租日）
            YearMonth currentMonth = YearMonth.now();
            LocalDate billDate = calculateBillDate(room.getRentPayDate(), currentMonth);

            // 4. 查询该房间本月是否已有账单
            Map<String, Object> params = new HashMap<>();
            String billDateStart = dateFormatter.format(currentMonth.atDay(1));
            String billDateEnd = dateFormatter.format(currentMonth.atEndOfMonth());
            params.put("billDateStart", billDateStart);
            params.put("billDateEnd", billDateEnd);

            QifBill qifBillQuery = new QifBill();
            qifBillQuery.setRoomId(room.getRoomId());
            qifBillQuery.setParams(params);
            List<QifBill> existingBills = qifBillMapper.selectQifBillList(qifBillQuery);

            // 5. 构建账单数据
            QifBill billData = buildBillData(room, billDate);

            // 6. 生成「25101601201」格式的字符串，再转为Long类型编号
            String billNumberStr = generateBillNumberStr(room, billDate);
            Long billNumber = convertToLong(billNumberStr); // 关键：字符串转Long

            // 7. 存在则更新（保持原编号），不存在则新增（确保编号唯一）
            if (existingBills != null && !existingBills.isEmpty()) {
                QifBill existingBill = existingBills.get(0);
                billData.setBillId(existingBill.getBillId());
                billData.setBillNumber(existingBill.getBillNumber()); // 更新不改变编号
                billData.setUpdateTime(new Date());
                billData.setUpdateBy(getLoginName());
                qifBillMapper.updateQifBill(billData);
                operateCount++;
            } else {
                // 二次校验Long编号唯一性（避免冲突）
                if (checkBillNumberUnique(billNumber)) {
                    billData.setBillNumber(billNumber);
                    billData.setCreateTime(new Date());
                    billData.setCreateBy(getLoginName());
                    qifBillMapper.insertQifBill(billData);
                    operateCount++;
                } else {
                    // 极端冲突场景：追加1位序号（如25101601201→251016012011）
                    Long uniqueBillNumber = getUniqueBillNumber(billNumber);
                    billData.setBillNumber(uniqueBillNumber);
                    billData.setCreateTime(new Date());
                    billData.setCreateBy(getLoginName());
                    qifBillMapper.insertQifBill(billData);
                    operateCount++;
                }
            }
        }
        return operateCount;
    }

    /**
     * 构建账单核心数据（新增/更新共用逻辑抽取）
     * @param room 房间信息
     * @param billDate 计算后的账单日期
     * @return 组装好的账单对象
     */
    private QifBill buildBillData(QifRoom room, LocalDate billDate) {
        QifBill bill = new QifBill();

        // 1. 基础关联信息
        bill.setRoomId(room.getRoomId());
        bill.setUnit(room.getUnit());
        bill.setRoomNum(room.getRoomNum());
        // 转换LocalDate为Date类型，匹配billDate字段类型
        bill.setBillDate(Date.from(billDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));

        // 2. 租金相关信息
        bill.setRent(room.getRent());
        bill.setDeposit(room.getDeposit());//押金
        bill.setKeyCard(room.getKeyCard());//房卡押金
        bill.setRentPayDate(room.getRentPayDate()); // Long类型交租日直接赋值

        // 3. 水电表读数与用量计算
        bill.setLastMonthWater(room.getWater());
        bill.setLastMonthElectricity(room.getElectricity());
        // 注：本月读数默认与上月一致，实际场景需替换为真实抄表数据
        bill.setThisMonthWater(room.getWater());
        bill.setThisMonthElectricity(room.getElectricity());
        // 计算实际用量（确保非负）
        long waterUsage = bill.getThisMonthWater() - bill.getLastMonthWater();
        long electricityUsage = bill.getThisMonthElectricity() - bill.getLastMonthElectricity();
        bill.setWaterUsage(Math.max(0, waterUsage));
        bill.setElectricityUsage(Math.max(0, electricityUsage));

        // 4. 费用相关基础配置
        bill.setWaterUnitPrice(room.getWaterUnitPrice());
        bill.setElectricityUnitPrice(room.getElectricityUnitPrice());
        bill.setSanitationFee(room.getSanitationFee());
        // 5. 计算总费用
        calculateTotalFee(bill);

        // 6. 固定状态配置（是否结算(0：是 1：否)、未删除（0代表存在 2代表删除））
        bill.setIsSettlement("1");
        bill.setDelFlag("0");

        return bill;
    }
    /**
     * 生成「年2位+月2位+日2位+单元2位+莞e号3位」格式的字符串
     * 示例：2025-10-16、1单元、莞e号201 → "25101601201"
     */
    private String generateBillNumberStr(QifRoom room, LocalDate billDate) {
        // 1. 年（2位）：取年份后两位（2025→"25"）
        String year = String.format("%02d", billDate.getYear() % 100);
        // 2. 月（2位）：不足补0（1月→"01"，10月→"10"）
        String month = String.format("%02d", billDate.getMonthValue());
        // 3. 日（2位）：不足补0（5日→"05"，16日→"16"）
        String day = String.format("%02d", billDate.getDayOfMonth());
        // 4. 单元（2位）：空值补"00"，非空转两位（1→"01"，2→"02"）
        String unit = room.getUnit() != null ?
                String.format("%02d", Integer.parseInt(room.getUnit())) : "00";
        // 5. 莞e号（3位）：空值补"000"，不足补0（12→"012"，201→"201"）
        String roomENum = room.getRoomENum() != null ?
                String.format("%03d", room.getRoomENum()) : "000";

        // 拼接为11位字符串（2+2+2+2+3=11位）
        return year + month + day + unit + roomENum;
    }

    /**
     * 将11位编号字符串转为Long类型（避免数值溢出）
     * 注意：11位数字最大值为99999999999，小于Long最大值（9223372036854775807），可安全转换
     */
    private Long convertToLong(String billNumberStr) {
        try {
            return Long.parseLong(billNumberStr);
        } catch (NumberFormatException e) {
            // 极端情况：字符串格式异常，用当前时间戳兜底（确保生成有效Long）
            return System.currentTimeMillis();
        }
    }

    /**
     * 校验Long类型账单编号是否唯一
     */
    private boolean checkBillNumberUnique(Long billNumber) {
        if (billNumber == null) {
            return false;
        }
        // 调用Mapper查询该编号是否已存在
        return qifBillMapper.countBillNumber(billNumber) == 0;
    }

    /**
     * 极端冲突场景：获取唯一编号（在原编号基础上追加1位序号）
     * 示例：原编号25101601201→251016012011→251016012012...
     */
    private Long getUniqueBillNumber(Long baseNumber) {
        Long uniqueNumber = baseNumber;
        // 循环查询，直到找到未使用的编号
        while (qifBillMapper.countBillNumber(uniqueNumber) > 0) {
            uniqueNumber = uniqueNumber * 10 + 1; // 末尾追加1，避免重复
            // 安全校验：防止无限循环（超过18位则用时间戳兜底）
            if (uniqueNumber.toString().length() > 18) {
                uniqueNumber = System.currentTimeMillis();
            }
        }
        return uniqueNumber;
    }
    /**
     * 根据Long类型的交租日计算账单日期
     */
    private LocalDate calculateBillDate(Integer rentPayDate, YearMonth currentMonth) {
        // 处理Long类型交租日的空值和无效值
        if (rentPayDate == null || rentPayDate < 1) {
            rentPayDate = 1; // 默认为1号
        }

        // 转换为int进行计算（确保不超过int范围）
        int payDay = Math.min(rentPayDate, 31);
        int maxDay = currentMonth.atEndOfMonth().getDayOfMonth();
        int actualBillDay = Math.min(payDay, maxDay);

        return currentMonth.atDay(actualBillDay);
    }

    /**
     * 计算电费（返回BigDecimal类型）
     */
    private BigDecimal calculateElectricityFee(long usage, BigDecimal unitPrice) {
        if (unitPrice == null) {
            return BigDecimal.ZERO;
        }
        return unitPrice.multiply(new BigDecimal(usage))
                .setScale(2, BigDecimal.ROUND_HALF_UP); // 保留两位小数
    }

    /**
     * 计算总费用
     */
    private void calculateTotalFee(QifBill bill) {
        BigDecimal total = BigDecimal.ZERO;
        // 累加各项费用，处理null值为0
        total = total.add(convertNullToZero(bill.getRent()));
        total = total.add(convertNullToZero(bill.getWaterRate()));
        total = total.add(convertNullToZero(bill.getElectricityBill()));
        total = total.add(convertNullToZero(bill.getSanitationFee()));

        bill.setReceivable(total);
        bill.setActualPay(BigDecimal.ZERO); // 初始实收款为0
    }

    /**
     * 将可能为null的BigDecimal转换为0
     */
    private BigDecimal convertNullToZero(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }


    /**
     * 修改琦风公寓-每月账单
     *
     * @param qifBill 琦风公寓-每月账单
     * @return 结果
     */
    @Override
    public int updateQifBill(QifBill qifBill) {
        qifBill.setUpdateTime(DateUtils.getNowDate());
        return qifBillMapper.updateQifBill(qifBill);
    }

    /**
     * 批量删除琦风公寓-每月账单
     *
     * @param billIds 需要删除的琦风公寓-每月账单主键
     * @return 结果
     */
    @Override
    public int deleteQifBillByBillIds(String billIds) {
        return qifBillMapper.deleteQifBillByBillIds(Convert.toStrArray(billIds));
    }

    /**
     * 删除琦风公寓-每月账单信息
     *
     * @param billId 琦风公寓-每月账单主键
     * @return 结果
     */
    @Override
    public int deleteQifBillByBillId(Long billId) {
        return qifBillMapper.deleteQifBillByBillId(billId);
    }

    /**
     * 更新账单指定字段（本月水/本月电）
     */
    @Override
    @Transactional
    public void updateBillCell(Long billId, String fieldName, BigDecimal fieldValue) {
        // 2. 查询账单，不存在则抛异常
        QifBill bill = qifBillMapper.selectQifBillByBillId(billId);
        if (bill == null) {
            throw new ServiceException("账单不存在！");
        }

        // -------------------------- 核心标识：是否为单独修改结算状态 --------------------------
        boolean isOnlySettlementUpdate = "isSettlement".equals(fieldName);
        // 新增：标识是否需要同步房间水电（仅修改本月水/电时）
        boolean needSyncRoomMeter = "thisMonthWater".equals(fieldName) || "thisMonthElectricity".equals(fieldName);
        // 新增：暂存更新后的本月水/电值，用于后续同步
        Long syncWaterValue = null;
        Long syncElecValue = null;

        // 3. 根据字段名更新对应值（保留原有逻辑，新增暂存同步值）
        switch (fieldName) {
            // 整数字段：本月水（仅整数，不支持小数）
            case "thisMonthWater":
                if (fieldValue.scale() > 0 && fieldValue.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) != 0) {
                    throw new ServiceException("本月水量必须为整数！");
                }
                Long waterValue = fieldValue.longValue();
                bill.setThisMonthWater(waterValue);
                // 暂存本月水值，用于同步房间
                syncWaterValue = waterValue;

                // 计算实际用水量和水费（非结算状态修改时执行）
                if (bill.getLastMonthWater() != null) {
                    Long waterUsage = Math.max(0, waterValue - bill.getLastMonthWater());
                    bill.setWaterUsage(waterUsage);
                    BigDecimal waterUnitPrice = bill.getWaterUnitPrice() != null ?
                            bill.getWaterUnitPrice() : new BigDecimal("5.00");
                    BigDecimal waterRate = waterUnitPrice.multiply(new BigDecimal(waterUsage))
                            .setScale(2, RoundingMode.HALF_UP);
                    bill.setWaterRate(waterRate);
                } else {
                    bill.setWaterUsage(0L);
                    bill.setWaterRate(new BigDecimal("0.00"));
                }
                break;

            // 整数字段：本月电（仅整数，不支持小数）
            case "thisMonthElectricity":
                if (fieldValue.scale() > 0 && fieldValue.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) != 0) {
                    throw new ServiceException("本月电量必须为整数！");
                }
                Long elecValue = fieldValue.longValue();
                bill.setThisMonthElectricity(elecValue);
                // 暂存本月电值，用于同步房间
                syncElecValue = elecValue;

                // 计算实际用电量和电费（非结算状态修改时执行）
                if (bill.getLastMonthElectricity() != null) {
                    Long elecUsage = Math.max(0, elecValue - bill.getLastMonthElectricity());
                    bill.setElectricityUsage(elecUsage);
                    BigDecimal elecUnitPrice = bill.getElectricityUnitPrice() != null ?
                            bill.getElectricityUnitPrice() : new BigDecimal("1.30");
                    BigDecimal elecBill = elecUnitPrice.multiply(new BigDecimal(elecUsage))
                            .setScale(2, RoundingMode.HALF_UP);
                    bill.setElectricityBill(elecBill);
                } else {
                    bill.setElectricityUsage(0L);
                    bill.setElectricityBill(new BigDecimal("0.00"));
                }
                break;

            // 金额字段：实收款（支持小数，保留2位）
            case "actualPay":
                BigDecimal actualPay = fieldValue.setScale(2, RoundingMode.HALF_UP);
                bill.setActualPay(actualPay);
                break;

            // 状态字段：是否结算（单独修改时，仅更新状态，不触发计算）
            case "isSettlement":
                // 修正：异常提示文字（原“0是1否”反了，统一为“0=否，1=是”）
                if (fieldValue.scale() > 0 || (fieldValue.compareTo(BigDecimal.ZERO) != 0 && fieldValue.compareTo(BigDecimal.ONE) != 0)) {
                    throw new ServiceException("结算状态仅支持0（是）或1（否）！");
                }
                bill.setIsSettlement(fieldValue.longValue() + "");
                break;

            default:
                throw new ServiceException("不支持的更新字段：" + fieldName);
        }

        // -------------------------- 关键逻辑1：单独改结算状态时，跳过计算和联动 --------------------------
        if (!isOnlySettlementUpdate) {
            // 4. 重新计算应收款（仅非结算状态修改时执行）
            BigDecimal rent = bill.getRent() != null ? bill.getRent().setScale(2, RoundingMode.HALF_UP) : new BigDecimal("0.00");
            BigDecimal waterRate = bill.getWaterRate() != null ? bill.getWaterRate() : new BigDecimal("0.00");
            BigDecimal elecBill = bill.getElectricityBill() != null ? bill.getElectricityBill() : new BigDecimal("0.00");
            BigDecimal sanitationFee = bill.getSanitationFee() != null ? bill.getSanitationFee() : new BigDecimal("0.00");

            BigDecimal receivable = rent.add(waterRate)
                    .add(elecBill)
                    .add(sanitationFee)
                    .setScale(2, RoundingMode.HALF_UP);
            bill.setReceivable(receivable);

            // 5. 实收款-结算状态联动（修正逻辑：实收款=应收款时设为1（否），否则0（是））
            if (bill.getReceivable() != null && bill.getActualPay() != null) {
                if (bill.getActualPay().subtract(bill.getReceivable()).abs().compareTo(new BigDecimal("0.001")) < 0) {
                    bill.setIsSettlement("0"); // 金额一致→结算状态“是”
                } else {
                    bill.setIsSettlement("1"); // 金额不一致→结算状态“否”
                }
            } else {
                bill.setIsSettlement("1"); // 应收款/实收款为空→默认“否”
            }
        }

        // -------------------------- 关键逻辑2：同步更新房间水电数据（仅修改本月水/电时执行） --------------------------
        if (needSyncRoomMeter) {
            // 6. 校验房间关联字段（单元号、房间号不能为空）
            if (bill.getUnit() == null || bill.getRoomNum() == null || bill.getRoomNum().trim().isEmpty()) {
                throw new ServiceException("账单缺少单元号或房间号，无法同步房间水电数据！");
            }

            // 7. 查询对应房间（通过单元号+房间号定位唯一房间）
            QifRoom roomQuery = new QifRoom();
            roomQuery.setUnit(bill.getUnit()); // 单元号（与账单一致）
            roomQuery.setRoomNum(bill.getRoomNum()); // 房间号（与账单一致）
            List<QifRoom> roomList = qifRoomMapper.selectQifRoomList(roomQuery);
            if (roomList == null || roomList.size() != 1) {
                throw new ServiceException("未找到唯一对应的房间（单元：" + bill.getUnit() + "，房间：" + bill.getRoomNum() + "），无法同步！");
            }
            QifRoom targetRoom = roomList.get(0);

            // 8. 同步更新房间水电读数（仅更新修改的字段）
            if (syncWaterValue != null) {
                // 同步本月水：将房间表的“当前水表读数”更新为账单的本月水值
                targetRoom.setWater(syncWaterValue);
            }
            if (syncElecValue != null) {
                // 同步本月电：将房间表的“当前电表读数”更新为账单的本月电值
                targetRoom.setElectricity(syncElecValue);
            }

            // 9. 执行房间数据更新
            int roomUpdateRows = qifRoomMapper.updateQifRoom(targetRoom);
            if (roomUpdateRows == 0) {
                throw new ServiceException("同步更新房间水电数据失败！");
            }
        }

        // 10. 保存并校验账单更新结果
        int billUpdateRows = qifBillMapper.updateQifBill(bill);
        if (billUpdateRows == 0) {
            throw new ServiceException("账单更新失败，可能已被删除！");
        }
    }
}
